package com.goldman.aplt.util;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.goldman.aplt.base.constant.ApConstants;
import com.goldman.aplt.service.packet.ApsOutLogService;
import com.goldman.aplt.util.http.HTTPOption;
import com.goldman.aplt.util.http.HTTPResponse;
import com.goldman.http.common.s2s.S2SHttp;
import com.goldman.http.common.s2s.S2SOption;
import com.goldman.http.common.s2s.S2SResponse;
import com.goldman.http.common.s2s.interceptor.CustomRequestInterceptor;
import com.goldman.http.common.s2s.model.ResponseData;
import com.goldman.http.common.s2s.model.Status;
import com.goldman.http.common.s2s.model.WebThreadContext;
import com.goldman.http.common.s2s.util.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.apache.http.util.TextUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;

import static com.goldman.aplt.base.constant.ApConstants.CALL_OUT_TIMEOUT;

/**
 * 统一的外调请求工具
 */
@Component
@Slf4j
public class HttpUtil {

    @Autowired
    @Qualifier("apsOutLogService")
    private ApsOutLogService apsOutLogServiceImpl;
    private static ApsOutLogService apsOutLogService;
    private static CustomRequestInterceptor customInterceptor = new CustomRequestInterceptor();
    private static final int TIMEOUT = 60 * 1000;
    private static final String METHOD_GET = "GET";
    private static final String METHOD_POST = "POST";

    @PostConstruct
    public void init() {
        apsOutLogService = apsOutLogServiceImpl;
    }

    public static <D> S2SResponse<D> get(String url) {
        return S2SHttp.get(url);
    }

    public static <D> S2SResponse<D> get(String url, S2SOption s2SOption) {
        return S2SHttp.get(url, s2SOption);
    }

    public static <A, D> S2SResponse<D> post(String url, A a, String callSeq) {
        String beginTime = CommUtil.getComputerDateTime();
        S2SResponse<D> resp = S2SHttp.post(url, a, null);
        apsOutLogService.regOutLog(url, callSeq, beginTime, JsonUtil.toJsonStr(a), JsonUtil.toJsonStr(resp.getResponseData()), resp.getExecuteTime());
        return resp;
    }

    public static <A, D> S2SResponse<D> post(String url, A a) {
        String callSeq = ApSeqUtil.genSeq(ApConstants.CALL_SEQ_CODE);
        return post(url, a, callSeq);
    }

    public static <A, D> S2SResponse<D> post(String url, A a, S2SOption s2SOption) {
        String callSeq = ApSeqUtil.genSeq(ApConstants.CALL_SEQ_CODE);
        return post(url, a, callSeq, s2SOption);
    }

    public static <A, D> S2SResponse<D> post(String url, A a, String callSeq, S2SOption s2SOption) {

        String beginTime = CommUtil.getComputerDateTime();
        S2SResponse<D> resp = S2SHttp.post(url, a, s2SOption);
        String req = JsonUtil.toJsonStr(a).replaceAll("\\\\\"", "\"");

        if (req.startsWith("\"")) {
            req = req.substring(1);
        }

        if (req.endsWith("\"")) {
            req = req.substring(0, req.length() - 1);
        }

        apsOutLogService.regOutLog(url, callSeq, beginTime, req, JsonUtil.toJsonStr(resp.getResponseData()), resp.getExecuteTime());
        return resp;
    }

    public static <A, D> HTTPResponse<D> post(String url, A a, HTTPOption s2SOption) {
        return doPost(url, a, s2SOption);
    }

    public static <A, D> S2SResponse<D> postWithData(String url, A a, String callSeq) {
        String beginTime = CommUtil.getComputerDateTime();
        S2SResponse<D> resp = S2SHttp.postWithData(url, a, null);
        apsOutLogService.regOutLog(url, callSeq, beginTime, JsonUtil.toJsonStr(a), JsonUtil.toJsonStr(resp.getResponseData()), resp.getExecuteTime());
        return resp;
    }

    public static <A, D> S2SResponse<D> postWithData(String url, A a, String callSeq, S2SOption s2SOption) {
        String beginTime = CommUtil.getComputerDateTime();
        S2SResponse<D> resp = S2SHttp.postWithData(url, a, s2SOption);
        apsOutLogService.regOutLog(url, callSeq, beginTime, JsonUtil.toJsonStr(a), JsonUtil.toJsonStr(resp.getResponseData()), resp.getExecuteTime());
        return resp;
    }


    private static <A, D> HTTPResponse<D> doPost(String url, A a, HTTPOption s2SOption) {
        if (s2SOption == null) {
            s2SOption = HTTPOption.builder().build();
        }
        if (s2SOption.getContentType() != null) {
            Map<String, String> headers = s2SOption.getHeaders() == null ? new HashMap<>() : s2SOption.getHeaders();
            headers.put("Content-Type", s2SOption.getContentType().getContent());
            s2SOption.setHeaders(headers);
        }
        long beginTime = System.currentTimeMillis();
        HTTPResponse<D> s2SResponse = callPost(url, a, s2SOption);
        if (s2SResponse.getException() != null) {
            s2SResponse.setResponseData(ResponseData.sysError(s2SResponse.getExceptionPoint()));
        }
        s2SResponse.setExecuteTime(System.currentTimeMillis() - beginTime);
        logAccess(METHOD_POST, url, s2SOption, JsonUtil.toJsonStr(a), s2SResponse);
        return s2SResponse;
    }

    private static <A, D> HTTPResponse<D> callPost(String url, A a, HTTPOption s2SOption) {
        // 构建URI
        URIBuilder uriBuilder;
        try {
            uriBuilder = new URIBuilder(url);
        } catch (URISyntaxException e) {
            return HTTPResponse.<D>builder().httpStatus(-1).
                    exception(e).exceptionPoint("new URIBuilder occur URISyntaxException when callPost").build();
        }
        // 设置参数
        if (s2SOption.getParams() != null && s2SOption.getParams().keySet().size() > 0) {
            for (String key : s2SOption.getParams().keySet()) {
                uriBuilder.setParameter(key, s2SOption.getParams().get(key));
            }
        }
        // 构建HttpGet
        HttpPost httpPost;
        try {
            httpPost = new HttpPost(uriBuilder.build());
        } catch (URISyntaxException e) {
            return HTTPResponse.<D>builder().httpStatus(-1).
                    exception(e).exceptionPoint("new HttpPost occur URISyntaxException when callPost").build();
        }
        // 设置头
        if (s2SOption.headerIsNotEmpty()) {
            for (String key : s2SOption.getHeaders().keySet()) {
                httpPost.setHeader(key, s2SOption.getHeaders().get(key));
            }
        }
        // 请求配置：超时等。
        int timeout = s2SOption.getTimeout() > 0 ? s2SOption.getTimeout() : TIMEOUT;
        RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(timeout).build();
        httpPost.setConfig(requestConfig);
        // 设置消息体
        String requestBodyStr = null;
        if (a instanceof String) {
            requestBodyStr = (String) a;
        } else {
            requestBodyStr = JsonUtil.toJsonStr(a);
        }
        StringEntity requestEntity;
        try {
            requestEntity = new StringEntity(requestBodyStr);
        } catch (UnsupportedEncodingException e) {
            return HTTPResponse.<D>builder().httpStatus(-1).
                    exception(e).exceptionPoint("new StringEntity occur UnsupportedEncodingException when callPost").build();
        }
        httpPost.setEntity(requestEntity);
        // 执行请求
        CloseableHttpClient httpClient = HttpClients.custom()
                .addInterceptorFirst(customInterceptor)
                .setDefaultCookieStore(s2SOption.getCookieStore())
                .build();
        CloseableHttpResponse httpResponse = null;
        try {
            httpResponse = httpClient.execute(httpPost);
        } catch (IOException e) {
            close(httpClient, null);
            return HTTPResponse.<D>builder().httpResponse(httpResponse).httpStatus(-1).
                    exception(e).exceptionPoint("httpClient.execute occur IOException when callPost").build();
        }
        // 获取响应实体
        HttpEntity entity = httpResponse.getEntity();
        String responseRawData = "";
        try {
            responseRawData = EntityUtils.toString(entity, "utf-8");
        } catch (IOException e) {
            close(httpClient, httpResponse);
            return HTTPResponse.<D>builder().httpResponse(httpResponse).httpStatus(httpResponse.getStatusLine().getStatusCode()).
                    exception(e).exceptionPoint("EntityUtils.toString occur IOException when callPost").build();
        }
        // 反序列化响应体
        ResponseData<D> responseData;
        try {
            responseData = parseResponseData(s2SOption, responseRawData, httpResponse);
//            responseData.setHttpResponse(httpResponse);
        } catch (Exception e) {
            close(httpClient, httpResponse);
            return HTTPResponse.<D>builder().httpResponse(httpResponse).httpStatus(httpResponse.getStatusLine().getStatusCode()).
                    exception(e).exceptionPoint("parseResponseData occur Exception when callPost").
                    responseRawContent(responseRawData).build();
        }
        return HTTPResponse.<D>builder().httpResponse(httpResponse).httpStatus(httpResponse.getStatusLine().getStatusCode()).
                responseRawContent(responseRawData).responseData(responseData).build();
    }

    private static <D> void logAccess(String requestMethod, String url, HTTPOption s2SOption, String requestBody, HTTPResponse<D> s2SResponse) {
        Map<String, String> params = s2SOption.getParams() == null ? new HashMap<>() : s2SOption.getParams();
        Map<String, String> headers = s2SOption.getHeaders() == null ? new HashMap<>() : s2SOption.getHeaders();
        String requestId = WebThreadContext.getRequestId() == null ? "" : WebThreadContext.getRequestId();
        Object[] objs = new Object[]{requestMethod, url, s2SResponse.getHttpStatus(), s2SResponse.getResponseData().getStatus().getCode(),
                s2SResponse.getExecuteTime(), requestId, headers, params, requestBody,
                s2SResponse.getResponseRawContent(), JsonUtil.toJsonStr(s2SResponse.getResponseData())};
        String logFormat = "[S2SHttp-{}] url={} httpCode={} code={} executeTime={} requestId={} header={} params={} requestBody={} responseRawContent={} responseData={}";
        if (s2SResponse.getResponseData().isSuccess() ||
                (s2SOption.isIgnoreErrCode(s2SResponse.getResponseData().getStatus().getCode()))) {
            log.info(logFormat, objs);
        } else {
            log.error(logFormat, objs);
        }
    }

    private static <D> ResponseData<D> parseResponseData(HTTPOption s2SOption, String responseRawData, CloseableHttpResponse httpResponse) {
        if (httpResponse.getStatusLine().getStatusCode() >= 400) {
            return ResponseData.sysError("Http response status code is " + httpResponse.getStatusLine().getStatusCode());
        }
        String contentType = httpResponse.getFirstHeader("Content-Type") == null ? "" : httpResponse.getFirstHeader("Content-Type").getValue();
        contentType = TextUtils.isEmpty(contentType) ? s2SOption.getContentType().getContent() : contentType;
        if (contentType == null || !contentType.toLowerCase().contains("json")) {
            return ResponseData.success((D) responseRawData);
        }
        JsonNode jsonNode = JsonUtil.toTree(responseRawData);
        if (jsonNode.has("status") && jsonNode.get("status").isObject()) {
            // 响应体，{"status":{"code":1, "msg":"xxxxx"}, "data":任何类型}
            return JsonUtil.toObject(responseRawData, new TypeReference<ResponseData<D>>() {
            });
        } else if (jsonNode.has("code") && jsonNode.has("data")) {
            // 响应体，{"code":1, "msg":"xxx", data:任何类型}  或者 响应体，{"code":"0000", "msg":"xxx", data:任何类型}
            int code = Integer.valueOf(jsonNode.get("code").toString());
            String msg = jsonNode.has("msg") ? jsonNode.get("msg").toString() : "";
            String err = jsonNode.has("err") ? jsonNode.get("err").toString() : "";
            ResponseData responseData = JsonUtil.toObject(responseRawData, new TypeReference<ResponseData<D>>() {
            });
            responseData.setStatus(new Status(code, msg, err));
            return responseData;
        } else {
            // 响应体，{"code":1, ...} 或者 响应体，{...}
            int code = jsonNode.has("code") ? Integer.valueOf(jsonNode.get("code").toString()) : 0;
            String msg = jsonNode.has("msg") ? jsonNode.get("msg").toString() : "";
            String err = jsonNode.has("err") ? jsonNode.get("err").toString() : "";
            D data = JsonUtil.toObject(responseRawData, new TypeReference<D>() {
            });
            return new ResponseData<>(new Status(code, msg, err), data);
        }
    }

    private static void close(CloseableHttpClient httpClient, CloseableHttpResponse httpResponse) {
        if (httpResponse != null) {
            try {
                httpResponse.close();
            } catch (IOException e) {
                log.error("", e);
            }
        }
        if (httpClient != null) {
            try {
                httpClient.close();
            } catch (IOException e) {
                log.error("", e);
            }
        }
    }

    public static boolean doPostDownLoad(String url, Map params, String filePath, HTTPOption s2SOption) throws IOException {
        try {
            // 定义HttpClient
            CloseableHttpClient client = HttpClients.custom()
                    .addInterceptorFirst(customInterceptor)
                    .setDefaultCookieStore(s2SOption.getCookieStore())
                    .build();
            // 实例化HTTP方法
            HttpPost request = new HttpPost();
            request.setHeader("Content-Type", s2SOption.getContentType().getContent());
            request.setURI(new URI(url));

            //设置参数
            if (params != null) {
                request.setEntity(new StringEntity(JsonUtil.toJsonStr(params)));
            }
            HttpResponse response = client.execute(request);
            int code = response.getStatusLine().getStatusCode();
            if (code == 200) {    //请求成功
                OutputStream output = new FileOutputStream(filePath);
                response.getEntity().writeTo(output);
                return true;
            } else {
                log.error("doPostDownLoad fail, resp code{}", code);
                return false;
            }
        } catch (Exception e) {
            log.error("doPostDownLoad exception", e);
            return false;
        }
    }
}
